package il.ac.tau.cg.ex3.math.shapes;

import il.ac.tau.cg.ex3.math.Params;
import il.ac.tau.cg.ex3.math.Point;
import il.ac.tau.cg.ex3.math.Vector;
import il.ac.tau.cg.ex3.utils.octree.BoundingBox;

import java.util.SortedSet;
import java.util.TreeSet;

public class Cylinder extends Shape
{
	private Point start;
	private Vector axis;
	private double radius;
	
	public Cylinder(Point start, Vector axis, double radius)
	{
		this.start = start;
		this.axis = axis;
		this.radius = radius;
	}
	
	public void setStart(Point start)
	{
		this.start = start;
	}
	
	public Point getStart()
	{
		return start;
	}
	
	public void setAxis(Vector axis)
	{
		this.axis = axis;
	}
	
	public Vector getAxis()
	{
		return axis;
	}
	
	public void setRadius(double radius)
	{
		this.radius = radius;
	}
	
	public double getRadius()
	{
		return radius;
	}
	
	public Vector getDirection()
	{
		return axis.normalize();
	}
	
	public double getLength()
	{
		return axis.length();
	}
	
	@Override
	public SortedSet<Double> intersect(Point origin, Vector direction)
	{
		SortedSet<Double> result = new TreeSet<Double>();
		
		Vector x = origin.subtract(start).cross(axis);
		Vector y = direction.cross(axis);
		
		double z = (radius * radius) * axis.dot(axis);
		
		double a = y.dot(y);
		double b = 2.0 * x.dot(y);
		double c = x.dot(x) - z;
		
		double d = (b * b) - 4 * a * c;
		
		if (d < 0.0)
		{
			return result;
		}
		
		double t1 = (-b + Math.sqrt(d)) / (2.0 * a);
		double t2 = (-b - Math.sqrt(d)) / (2.0 * a);
		
		if (t1 >= 0.0)
		{
			Point p = origin.add(direction.scale(t1));
			
			Vector v1 = p.subtract(start);
			Vector v2 = p.subtract(start.add(axis));
			
			if ((v1.dot(axis) > 0.0) && (v2.dot(axis.negate()) > 0.0))
			{
				result.add(t1);
			}
		}
		
		if (t2 >= 0.0)
		{
			Point p = origin.add(direction.scale(t2));
			
			Vector v1 = p.subtract(start);
			Vector v2 = p.subtract(start.add(axis));
			
			if ((v1.dot(axis) > 0.0) && (v2.dot(axis.negate()) > 0.0))
			{
				result.add(t2);
			}
		}
		
		return result;
	}

	@Override
	public Vector getNormal(Point p)
	{
		Vector v = new Vector(start, p);
		Vector helper = v.cross(axis);
		
		return axis.cross(helper).normalize();
	}

	@Override
	public Params getParams(Point p)
	{
		Vector vec = new Vector(start, p);
		
		Vector ortho = axis.cross(vec.cross(axis)).normalize();
		Vector ao1 = axis.arbitraryOrthogonal().normalize();
		Vector ao2 = ao1.cross(axis).normalize();
		
		double c = ortho.dot(ao1);
		double s = Math.acos(ortho.dot(ao2));
		
		double u = vec.dot(axis) / Math.pow(axis.length(), 2.0);
		double v = (Math.PI + ((c > 0.0) ? s : -s)) / (2.0 * Math.PI);
		
		u = Math.max(Math.min(u, 1.0), 0.0);
		v = Math.max(Math.min(v, 1.0), 0.0);
		
		return new Params(u, v);
	}

	@Override
	public BoundingBox getBoundingBox()
	{
		Disc disc1 = new Disc(start, axis.normalize().scale(-1.0), radius);
		Disc disc2 = new Disc(start.add(axis), axis.normalize(), radius);
		
		BoundingBox box1 = disc1.getBoundingBox();
		BoundingBox box2 = disc2.getBoundingBox();
		
		double minX = Math.min(box1.getMinX(), box2.getMinX());
		double maxX = Math.max(box1.getMaxX(), box2.getMaxX());
		double minY = Math.min(box1.getMinY(), box2.getMinY());
		double maxY = Math.max(box1.getMaxY(), box2.getMaxY());
		double minZ = Math.min(box1.getMinZ(), box2.getMinZ());
		double maxZ = Math.max(box1.getMaxZ(), box2.getMaxZ());
		
		return new BoundingBox(minX, maxX, minY, maxY, minZ, maxZ);
	}
}
